#include "../stdafx.h"
#include "XPM.h"
#include "../include/platform.h"

namespace soy {

	static const char *NextField(const char *s) {
		// In case there are leading spaces in the string
		while (*s && *s == ' ') {
			s++;
		}
		while (*s && *s != ' ') {
			s++;
		}
		while (*s && *s == ' ') {
			s++;
		}
		return s;
	}

	// Data lines in XPM can be terminated either with NUL or "
	static size_t MeasureLength(const char *s) {
		size_t i = 0;
		while (s[i] && (s[i] != '\"'))
			i++;
		return i;
	}

	ColourAllocated XPM::ColourFromCode(int ch) const {
		return colourCodeTable[ch]->allocated;
#ifdef SLOW
		for (int i=0; i<nColours; i++) {
			if (codes[i] == ch) {
				return colours[i].allocated;
			}
		}
		return colours[0].allocated;
#endif
	}

	void XPM::FillRun(Surface *surface, int code, int startX, int y, int x) {
		if ((code != codeTransparent) && (startX != x)) {
			Rect rc(startX, y, x, y+1);
			surface->FillRectangle(rc, ColourFromCode(code));
		}
	}

	XPM::XPM(const char *textForm) :
	data(0), codes(0), colours(0), lines(0) {
		Init(textForm);
	}

	XPM::XPM(const char *const *linesForm) :
	data(0), codes(0), colours(0), lines(0) {
		Init(linesForm);
	}

	XPM::~XPM() {
		Clear();
	}

	void XPM::Init(const char *textForm) {
		Clear();
		// Test done is two parts to avoid possibility of overstepping the memory
		// if memcmp implemented strangely. Must be 4 bytes at least at destination.
		if ((0 == memcmp(textForm, "/* X", 4)) && (0 == memcmp(textForm, "/* XPM */", 9))) {
			// Build the lines form out of the text form
			const char **linesForm = LinesFormFromTextForm(textForm);
			if (linesForm != 0) {
				Init(linesForm);
				delete []linesForm;
			}
		} else {
			// It is really in line form
			Init(reinterpret_cast<const char * const *>(textForm));
		}
	}

	void XPM::Init(const char *const *linesForm) {
		Clear();
		height = 1;
		width = 1;
		nColours = 1;
		data = NULL;
		codeTransparent = ' ';
		codes = NULL;
		colours = NULL;
		lines = NULL;
		if (!linesForm)
			return;

		const char *line0 = linesForm[0];
		width = atoi(line0);
		line0 = NextField(line0);
		height = atoi(line0);
		line0 = NextField(line0);
		nColours = atoi(line0);
		line0 = NextField(line0);
		if (atoi(line0) != 1) {
			// Only one char per pixel is supported
			return;
		}
		codes = new char[nColours];
		colours = new ColourPair[nColours];

		int strings = 1+height+nColours;
		lines = new char *[strings];
		size_t allocation = 0;
		for (int i=0; i<strings; i++) {
			allocation += MeasureLength(linesForm[i]) + 1;
		}
		data = new char[allocation];
		char *nextBit = data;
		for (int j=0; j<strings; j++) {
			lines[j] = nextBit;
			size_t len = MeasureLength(linesForm[j]);
			memcpy(nextBit, linesForm[j], len);
			nextBit += len;
			*nextBit++ = '\0';
		}

		for (int code=0; code<256; code++) {
			colourCodeTable[code] = 0;
		}

		for (int c=0; c<nColours; c++) {
			const char *colourDef = linesForm[c+1];
			codes[c] = colourDef[0];
			colourDef += 4;
			if (*colourDef == '#') {
				colours[c].desired.Set(colourDef);
			} else {
				colours[c].desired = Color(0xff, 0xff, 0xff);
				codeTransparent = codes[c];
			}
			colourCodeTable[static_cast<unsigned char>(codes[c])] = &(colours[c]);
		}
	}

	void XPM::Clear() {
		delete []data;
		data = 0;
		delete []codes;
		codes = 0;
		delete []colours;
		colours = 0;
		delete []lines;
		lines = 0;
	}

	void XPM::RefreshColourPalette(Palette &pal, bool want) {
		if (!data || !codes || !colours || !lines) {
			return;
		}
		for (int i=0; i<nColours; i++) {
			pal.WantFind(colours[i], want);
		}
	}

	void XPM::CopyDesiredColours() {
		if (!data || !codes || !colours || !lines) {
			return;
		}
		for (int i=0; i<nColours; i++) {
			colours[i].Copy();
		}
	}

	void XPM::Draw(Surface *surface, Rect &rc) {
		if (!data || !codes || !colours || !lines) {
			return;
		}
		// Centre the pixmap
		int startY = rc.top + (rc.Height() - height) / 2;
		int startX = rc.left + (rc.Width() - width) / 2;
		for (int y=0; y<height; y++) {
			int prevCode = 0;
			int xStartRun = 0;
			for (int x=0; x<width; x++) {
				int code = lines[y+nColours+1][x];
				if (code != prevCode) {
					FillRun(surface, prevCode, startX + xStartRun, startY + y, startX + x);
					xStartRun = x;
					prevCode = code;
				}
			}
			FillRun(surface, prevCode, startX + xStartRun, startY + y, startX + width);
		}
	}

	const char **XPM::LinesFormFromTextForm(const char *textForm) {
		// Build the lines form out of the text form
		const char **linesForm = 0;
		int countQuotes = 0;
		int strings=1;
		int j=0;
		for (; countQuotes < (2*strings) && textForm[j] != '\0'; j++) {
			if (textForm[j] == '\"') {
				if (countQuotes == 0) {
					// First field: width, height, number of colors, chars per pixel
					const char *line0 = textForm + j + 1;
					// Skip width
					line0 = NextField(line0);
					// Add 1 line for each pixel of height
					strings += atoi(line0);
					line0 = NextField(line0);
					// Add 1 line for each colour
					strings += atoi(line0);
					linesForm = new const char *[strings];
					if (linesForm == 0) {
						break;	// Memory error!
					}
				}
				if (countQuotes / 2 >= strings) {
					break;	// Bad height or number of colors!
				}
				if ((countQuotes & 1) == 0) {
					linesForm[countQuotes / 2] = textForm + j + 1;
				}
				countQuotes++;
			}
		}
		if (textForm[j] == '\0' || countQuotes / 2 > strings) {
			// Malformed XPM! Height + number of colors too high or too low
			delete []linesForm;
			linesForm = 0;
		}
		return linesForm;
	}

	// In future, may want to minimize search time by sorting and using a binary search.

	XPMSet::XPMSet() : set(0), len(0), maximum(0), height(-1), width(-1) {
	}

	XPMSet::~XPMSet() {
		Clear();
	}

	void XPMSet::Clear() {
		for (int i = 0; i < len; i++) {
			delete set[i];
		}
		delete []set;
		set = 0;
		len = 0;
		maximum = 0;
		height = -1;
		width = -1;
	}

	void XPMSet::Add(int id, const char *textForm) {
		// Invalidate cached dimensions
		height = -1;
		width = -1;

		// Replace if this id already present
		for (int i = 0; i < len; i++) {
			if (set[i]->GetId() == id) {
				set[i]->Init(textForm);
				set[i]->CopyDesiredColours();
				return;
			}
		}

		// Not present, so add to end
		XPM *pxpm = new XPM(textForm);
		if (pxpm) {
			pxpm->SetId(id);
			pxpm->CopyDesiredColours();
			if (len == maximum) {
				maximum += 64;
				XPM **setNew = new XPM *[maximum];
				for (int i = 0; i < len; i++) {
					setNew[i] = set[i];
				}
				delete []set;
				set = setNew;
			}
			set[len] = pxpm;
			len++;
		}
	}

	XPM *XPMSet::Get(int id) {
		for (int i = 0; i < len; i++) {
			if (set[i]->GetId() == id) {
				return set[i];
			}
		}
		return 0;
	}

	int XPMSet::GetHeight() {
		if (height < 0) {
			for (int i = 0; i < len; i++) {
				if (height < set[i]->GetHeight()) {
					height = set[i]->GetHeight();
				}
			}
		}
		return (height > 0) ? height : 0;
	}

	int XPMSet::GetWidth() {
		if (width < 0) {
			for (int i = 0; i < len; i++) {
				if (width < set[i]->GetWidth()) {
					width = set[i]->GetWidth();
				}
			}
		}
		return (width > 0) ? width : 0;
	}


};
